<?php
/**
 * Svick a Fast Simple Smart PHP FrameWork
 * Author: Tommy 863758705@qq.com
 * Link: http://svick.tomener.com/
 * Since: 2022
 */

namespace Svick\Route;

use Closure;
use Svick\Config\Config;
use Svick\Core\App;
use Svick\Core\Request;
use Svick\Exception\CoreException;
use Svick\Helper\Helper;

class Router
{
    protected $routes = [];

    protected $route = [];

    protected $groupName = '';

    protected $path = '';

    /**
     * @throws CoreException
     */
    public function dispatch()
    {
        $this->path = rtrim(!isset($_SERVER['PATH_INFO']) ? '' : $_SERVER['PATH_INFO'], '/');

        //自动路由解析
        $this->autoParse();

        list($handler_name, $exist) = self::getHandlerName($this->route['controller']);
        if ($exist) {
            $handler = App::container()->get($handler_name);
            if (!method_exists($handler, $this->route['action'])) {
                $this->route = [];
            } else {
                $this->route['handler'] = $handler;
            }
        } else {
            $this->route = [];
        }

        //自动路由不存在
        if (empty($this->route)) {
            //加载路由缓存文件
            $route_cache_file = Helper::path('cache::routes/' . App::name() . '.php');
            if (is_file($route_cache_file)) {
                $this->routes = require $route_cache_file;
            } else {
                //加载路由文件
                $route_file = ROOT_PATH . 'routes' . DS . App::name() . '.php';
                if (is_file($route_file)) {
                    require $route_file;
                }
            }

            $this->parse();

            if (!empty($this->route)) {
                list($handler_name, $exist) = self::getHandlerName($this->route['controller']);

                if ($exist) {
                    $handler = App::container()->get($handler_name);
                    if (!method_exists($handler, $this->route['action'])) {
                        $this->route = [];
                    } else {
                        $this->route['handler'] = $handler;
                    }
                } else {
                    $this->route = [];
                }
            }
        }
    }

    /**
     * 路由解析
     *
     * @return void
     */
    public function parse()
    {
        $path = $this->path;

        $groups = array_keys($this->routes);
        $group_num = count($groups);
        if ($group_num == 1) {
            $this->routes = $this->routes[''];
        } elseif ($group_num > 1) {
            $seg = explode('/', $path, 3);
            $group = ! empty($seg[1]) ? '/' . $seg[1] : '';
            if (in_array($group, $groups)) {
                $this->routes = $this->routes[$group];
                $path = '/' . ($seg[2] ?? '');
            } else {
                $this->routes = $this->routes[''];
            }
        }

        foreach ($this->routes as $key => $route) {
            $request_method = Request::method();
            if (!in_array($request_method, $route['m'])) {
                continue;
            }

            $is_match = preg_match($route['r'], $path, $matches);
            if ($is_match == 1) {
                array_shift($matches);
                $params = $args = [];
                foreach ($route['pks'] as $idx => $param_key) {
                    if ($param_key[0] == 1 && empty($matches[$idx])) {
                        continue;
                    }
                    if (!empty($param_key[2])) {
                        $ret = preg_match('@^' . $param_key[2] . '$@', $matches[$idx]);
                        if (!$ret) {
                            return;
                        }
                        if (in_array($param_key[2], ['\d+', '\d*', '\d'])) {
                            $matches[$idx] = intval($matches[$idx]);
                        }
                    }
                    $params[$param_key[1]] = $matches[$idx];
                    $args[] = $matches[$idx];
                }

                $this->routes[$key]['params'] = $params;
                $this->routes[$key]['args'] = $args;
                $this->route = $this->routes[$key];
                if (is_array($route['c'])) {
                    $this->route['controller'] = str_replace('app\\'. App::name() .'\controller\\', '', $route['c'][0]);
                    $this->route['action'] = $route['c'][1];
                } elseif (is_string($route['c'])) {
                    $seg = explode('@', $route['c']);
                    $this->route['controller'] = ucfirst($seg[0]);
                    $this->route['action'] = $seg[1] ?? 'Index';
                }

                break;
            }
        }
    }

    /**
     * 自动路由解析
     *
     * @return void
     * @throws CoreException
     */
    private function autoParse()
    {
        $this->parseModule();
        self::loadAppConfig();

        $path = trim($this->path, '/');

        $url = Config::runtime('url');
        $controller = $url['c'];
        $action = $url['a'];

        if ($url['ext'] != '/') {
            $pos = strrpos($path, $url['ext']);
            if ($pos !== false) {
                $path = substr($path, 0, $pos);
            }
        }

        if (!empty($path)) {
            $params = explode('/', $path);
            $len = count($params);
            if ($len > 1) {
                $controller_arr = array_slice($params, 0, $len - 1);
                $controller_arr[$len - 2] = ucfirst($controller_arr[$len - 2]);
                $controller = implode('\\', $controller_arr);
                $action = $params[$len - 1];
            } else {
                $controller = ucfirst($params[0]);
            }
        }

        $this->route['controller'] = $controller;
        $this->route['action'] = $action;
        $this->route['params'] = [];
        $this->route['args'] = [];
    }

    /**
     * 添加路由
     *
     * @param $methods
     * @param $uri
     * @param $callback
     * @return void
     */
    public function add($methods, $uri, $callback)
    {
        if ($callback instanceof Closure) {
            die('[' . $uri . ']' . ' Framework Route does not support closure functions' . PHP_EOL);
        }
        $params_keys = [];
        preg_match_all('@{([^{}]+)}@', $uri, $matches);
        foreach ($matches[1] as $match) {
            $type = 0;
            $seg = explode(':', $match);
            $regex = !isset($seg[1]) ? '' : $seg[1];
            $pos = mb_strpos($seg[0], '?');
            if ($pos > 0) {
                $seg[0] = mb_substr($seg[0], 0, -1);
                $type = 1;
            }
            $params_keys[] = [$type, $seg[0], $regex]; //type 0必选参数 1可选参数
        }

        array_walk($methods, function (&$method) {
            $method = strtoupper($method);
        });

        $this->routes[$this->groupName][$uri] = [
            'm' => $methods,
            'pks' => $params_keys,
            'r' => preg_replace(['@{([^?{}:]+?)}@', '@/{([^?{}:]+?)\?}@', '@/{([^?{}:]+?):([^{}]+?)}@', '@/{([^?{}:]+?)\?:([^{}]+?)}@'], ['([^/]+)', '/?([^/]*?)', '/?($2)', '/?($2?)'], '@^' . $uri . '$@'),
            'c' => $callback,
        ];
    }

    public function addGroup($name, callable $callback)
    {
        $this->groupName = $name;
        $callback();
        $this->groupName = '';
    }

    /**
     * 模块解析
     *
     * @return void
     * @throws CoreException
     */
    private function parseModule(): void
    {
        $project_mode = Config::runtime('project.mode');
        $app = Config::runtime('project.default_app');
        switch ($project_mode) {
            case 0:
                break;
            case 1:
                $modules = Config::runtime('project.apps');
                if ($modules) {
                    $path = explode('/', $this->path);
                    if (isset($path[1]) && isset($modules[$path[1]])) {
                        $app = $modules[$path[1]];
                        $this->path = substr_replace($this->path, '', 0, strlen($path[1]) + 1);
                        if (empty($this->path)) {
                            $this->path = '/';
                        }
                    }
                }
                break;
            case 2:
                $version = Request::header('Version');
                if (!$version) {
                    throw new CoreException("need version Header");
                }
                $temp = explode('.', $version);
                if (count($temp) > 1) {
                    $app = $temp[0] . '\\' . implode('_', $temp);
                } else {
                    $app = $temp[0];
                }
                break;
        }
        App::setName($app);
    }

    /*
     * 加载应用配置
     */
    public static function loadAppConfig()
    {
        $app_config = App::configPath(App::name() . '.config.php');
        if (file_exists($app_config)) {
            Config::load($app_config, 'runtime');
        }
    }

    /**
     * 获取处理器名称
     *
     * @param $controller
     * @return array
     */
    protected static function getHandlerName($controller): array
    {
        $is_exist = true;
        $name = 'app\\' . App::name() . '\\controller\\' . $controller;
        $class_file = ROOT_PATH . str_replace('\\', DIRECTORY_SEPARATOR, $name) . '.php';

        if (!file_exists($class_file)) {
            $pos = strrpos($controller, '\\');
            $controller_file = substr($controller, $pos === false ? 0 : $pos + 1);
            $name = 'app\\' . App::name() . '\\controller\\' . strtolower($controller) . '\\' . $controller_file;
            $class_file = ROOT_PATH . str_replace('\\', DIRECTORY_SEPARATOR, $name) . '.php';
            if (!file_exists($class_file)) {
                $is_exist = false;
            }
        }
        return [$name, $is_exist];
    }

    public function getRoutes()
    {
        return $this->routes;
    }

    public function getRoute()
    {
        return $this->route;
    }
}
